/********************************** (C) COPYRIGHT *******************************
* File Name          : modbus_slave.c
* Author             : nx666
* Version            : V1.0.0
* Date               : 2023/07/22
* Description        : 实现modbus从机
*******************************************************************************/
/***********************************头文件区域***********************************/
#include "common_type.h"
#include "modbus.h"
#include "crc16.h"
#include "interact.h"

/***********************************类型定义*********************************/

/***********************************宏定义区***********************************/          
#define REG_ADDR_HIGH 		(2)
#define REG_ADDR_LOW 		(3)
#define REG_ADDR_BASE      (0x1000)
#define REG_QUAN_HIGH       (4)
#define REG_QUAN_LOW        (5)

//写多个寄存器
#define REG_START        (7)


/******************************** *全局变量区**********************************/
static modbus_ modbus;
static uInt16  modbusRegs[MODBUS_SLAVE_REG_NUM] = {0};

void modbus_send_bytes(uInt8 *buf,uInt16 len)
{ 
	logSend(buf, len);
}


/*********************************************************************
 * @fn      modbus_recv_bytes
 *
 * @brief   查询方式接收数据
 * 
 *
 * @return  void
 *          
 */
void modbus_recv_bytes(void)
{
	int i;
	i = foc_msgRxBufRead(&modbus.rcbuf[0], MODBUS_SLAVE_RCBUF);
	if (i > 0)
	{
		modbus.reflag = 1;
		modbus.recount = i;
		//modbus_send_bytes(modbus.rcbuf,i);
	}
}


uInt16 modbus_crc(uInt8 *data, int datalen)
{
    uInt16 ret;
	ret = CRC16_MODBUS(data,datalen);
    return ((ret & 0xFF) << 8)|((ret & 0xFF00) >> 8);
}

void modbus_cmd0x3()
{
   	uInt16 Regadd,Reglen,crc;
 	uInt8 i,j;	
	modbus_ *pmodbus = &modbus;

 	//得到要读取寄存器的首地址
 	Regadd = (pmodbus->rcbuf[REG_ADDR_HIGH] << 8)  | pmodbus->rcbuf[REG_ADDR_LOW];
 	//得到要读取寄存器的数量
 	Reglen = (pmodbus->rcbuf[REG_QUAN_HIGH] << 8) | pmodbus->rcbuf[REG_QUAN_LOW];

 	//发送回应数据包
 	i = 0;
	////ID号：发送本机设备地址
 	pmodbus->sendbuf[i++] = pmodbus->myadd;
	////发送功能码
 	pmodbus->sendbuf[i++] = 0x03;
	////返回数据的字节个数
   	pmodbus->sendbuf[i++] = ((Reglen * 2) % 246);
	////返回数据内容
 	for(j=0; (j < Reglen)&&(j < MODBUS_SLAVE_REG_NUM); j++)
 	{
 		//读modbus寄存器，如果地址超范围则返回0xFFFF
		if ((Regadd + j < REG_ADDR_BASE)||(Regadd + j >= REG_ADDR_BASE + MODBUS_SLAVE_REG_NUM))
		{
			pmodbus->sendbuf[i++] = 0xFF;
			pmodbus->sendbuf[i++] = 0xFF;
			continue;
		}
 	  	pmodbus->sendbuf[i++] = modbusRegs[Regadd - REG_ADDR_BASE +j] >> 8; 		//高位数据
 		pmodbus->sendbuf[i++] = modbusRegs[Regadd - REG_ADDR_BASE +j] & 0x00FF;	//低位数据
 	}
 	crc = modbus_crc(pmodbus->sendbuf,i);    //计算要返回数据的CRC
 	pmodbus->sendbuf[i++] = crc >> 8;		//校验位高位
 	pmodbus->sendbuf[i++] = crc & 0x00FF;	//校验位低位
 	
 	// 开始返回Modbus数据
 	modbus_send_bytes(pmodbus->sendbuf, i);
 	
 }

void modbus_cmd0x6()
{
    uInt16 Regadd;    //地址16位
 	uInt16 val;       //值
 	uInt16 i,crc;
	modbus_ *pmodbus = &modbus;

	//要写的寄存器地址
    Regadd = (pmodbus->rcbuf[REG_ADDR_HIGH] << 8)  | pmodbus->rcbuf[REG_ADDR_LOW];
	//待设置的寄存器值
 	val = (pmodbus->rcbuf[4] << 8)  | pmodbus->rcbuf[5];
	//修改寄存器
	if ((Regadd >= REG_ADDR_BASE)&&(Regadd < REG_ADDR_BASE + MODBUS_SLAVE_REG_NUM))
	{
		modbusRegs[Regadd - REG_ADDR_BASE]=val;
	}
	else 
	{
		val = 0xFFFF;
	}
 	
 	//打包回应主机的数据帧
	i = 0;
 	pmodbus->sendbuf[i++] = pmodbus->myadd;		//本设备地址
    pmodbus->sendbuf[i++] = 0x06;        		//功能码 
    pmodbus->sendbuf[i++] = (Regadd >> 8);  	//写入的地址
 	pmodbus->sendbuf[i++] = (Regadd & 0xFF);
 	pmodbus->sendbuf[i++] = (val >> 8);		//写入的数值
 	pmodbus->sendbuf[i++] = (val & 0xFF);
 	crc = modbus_crc(pmodbus->sendbuf,i); //获取crc校验位
 	pmodbus->sendbuf[i++] = (crc >> 8);  //crc校验位加入包中
 	pmodbus->sendbuf[i++] = (crc & 0xFF);
 	
 	// 开始返回Modbus数据
 	modbus_send_bytes(pmodbus->sendbuf, i);
 }

void modbus_cmd0x10()
{
	uInt16 Regadd;	//地址16位
	uInt16 Reglen;
	uInt16 i,crc;
	modbus_ *pmodbus = &modbus;
	
 	//得到要读取寄存器的首地址
 	Regadd = (pmodbus->rcbuf[REG_ADDR_HIGH] << 8)  | pmodbus->rcbuf[REG_ADDR_LOW];
 	//得到要读取寄存器的数量
 	Reglen = (pmodbus->rcbuf[REG_QUAN_HIGH] << 8) | pmodbus->rcbuf[REG_QUAN_LOW];
	//写modbus寄存器
	for(i=0; i < Reglen;i++)
	{
		//超地址范围的数据不写入
		if ((Regadd + i < REG_ADDR_BASE)||(Regadd + i >= REG_ADDR_BASE + MODBUS_SLAVE_REG_NUM))
		{
			continue;
		}
		modbusRegs[Regadd - REG_ADDR_BASE + i] = (pmodbus->rcbuf[REG_START + i * 2] >> 8)|(pmodbus->rcbuf[REG_START + 1 + i * 2]);
	}
	
	//准备回应主机的数据帧
	//内容=接收数组的前6位+两位的校验位
	pmodbus->sendbuf[0] = pmodbus->rcbuf[0];  //本设备地址
	pmodbus->sendbuf[1] = pmodbus->rcbuf[1];  //功能码 
	pmodbus->sendbuf[2] = pmodbus->rcbuf[2];  //写入的起始地址
	pmodbus->sendbuf[3] = pmodbus->rcbuf[3];
	pmodbus->sendbuf[4] = pmodbus->rcbuf[4];  //写入长度
	pmodbus->sendbuf[5] = pmodbus->rcbuf[5];
	crc = modbus_crc(pmodbus->sendbuf,6);//获取crc校验位
	pmodbus->sendbuf[6]=crc/256;  //crc校验位加入包中
	pmodbus->sendbuf[7]=crc%256;
	
	// 开始返回Modbus数据
 	modbus_send_bytes(pmodbus->sendbuf, i);
}

/*********************************************************************
 * @fn      modbus_event
 *
 * @brief   modbus事件处理
 * 
 *
 * @return  void
 *          
 */
void modbus_event(void)
 {
    //crc和接收到的crc
 	uInt16 crc,rccrc;
	modbus_ *pmodbus = &modbus;
 	//没有收到数据包
   if(pmodbus->reflag == 0) 
 	{
 	   return;
 	}
 	//收到数据包(接收完成)
 	//通过读到的数据帧计算CRC
 	//参数1是数组首地址，参数2是要计算的长度（除了CRC校验位其余全算）
 	crc = modbus_crc(&pmodbus->rcbuf[0],pmodbus->recount-2); //获取CRC校验位
 	// 读取数据帧的CRC
 	rccrc = (pmodbus->rcbuf[pmodbus->recount-2] << 8) | (pmodbus->rcbuf[pmodbus->recount-1]); //计算读取的CRC校验位
 	//等价于下面这条语句
 	if(crc == rccrc) //CRC检验成功 开始分析包
 	{	
 	   if(pmodbus->rcbuf[0] == pmodbus->myadd)  // 检查地址是否时自己的地址
 		{
 		   switch(pmodbus->rcbuf[1])   //分析modbus功能码
 			 {
 			    case 0:             break;
 				case 1:             break;
 				case 2:             break;
                //这是读取寄存器的数据
 				case 3:
                {
                    modbus_cmd0x3();
                    break;
                }
 				case 4:             break;
 				case 5:             break;
                //这是写入单个寄存器数据
                case 6:
                {
                    modbus_cmd0x6();
                    break;
                }
 				case 7:             break;
 				case 8:             break;
 				case 9:             break;
                //写入多个寄存器数据
 				case 16:
                {
                    modbus_cmd0x10();
                    break;
                }
 			 }
 		}
 		else if (pmodbus->rcbuf[0] == 0)
 		{
 		    //广播地址不予回应
 		}	 
 	}	
 	pmodbus->recount = 0;//接收计数清零
    pmodbus->reflag = 0; //接收标志清零
 }

/*********************************************************************
 * @fn      modbus_slave_init
 *
 * @brief   modbus slave初始化
 * 
 *
 * @return  modbus寄存器地址
 *          
 */
uInt16 *modbus_slave_init(uInt16 devAddr)
{
	modbus.myadd = devAddr;
	return modbusRegs;
}